package com.slowfly.rule.engine.controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.slowfly.rule.engine.component.SnowFlakeComponent;
import com.slowfly.rule.engine.constants.IRiskConstants;
import com.slowfly.rule.engine.entity.Decision;
import com.slowfly.rule.engine.entity.Scene;
import com.slowfly.rule.engine.entity.Strategy;
import com.slowfly.rule.engine.enums.ResultTypeEnum;
import com.slowfly.rule.engine.enums.StrategyStatusEnum;
import com.slowfly.rule.engine.model.FeatureValue;
import com.slowfly.rule.engine.param.ExecuteRiskParam;
import com.slowfly.rule.engine.result.ExecuteRiskResult;
import com.slowfly.rule.engine.service.*;
import com.slowfly.rule.engine.util.GroovyScriptUtil;
import com.slowfly.rule.engine.util.JsonUtils;
import com.slowfly.rule.engine.util.TextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;

/**
 * 风控引擎
 */
@Slf4j
@RestController("/rule/engine")
public class RuleEngineController {

    @Autowired
    private ISceneService sceneService;
    @Autowired
    private IStrategyService strategyService;
    @Autowired
    private IRuleService ruleService;
    @Autowired
    private IDecisionService decisionService;
    @Autowired
    private IFeatureService featureService;
    @Autowired
    private SnowFlakeComponent snowFlakeComponent;
    @Autowired
    private IRecordService recordService;
    @Autowired
    private IStrategyRecordService strategyRecordService;

    /**
     * 风控入口
     */
    public ExecuteRiskResult executeRisk(ExecuteRiskParam param) {
        long start = System.currentTimeMillis();
        long riskId = snowFlakeComponent.snowflakeId();
        // 查询场景
        Scene scene = sceneService.getByCode(param.getSceneCode());
        if (Objects.isNull(scene)) {
            return getNotHitResult(riskId, "场景编码不存在，" + param.getSceneCode());
        }
        // 查询策略
        List<Strategy> strategies = strategyService.listStrategy(scene);
        if (CollectionUtils.isEmpty(strategies)) {
            ExecuteRiskResult result = getNotHitResult(riskId, "没有上线或标记状态的策略，" + scene.getCode());
            recordService.save(riskId, start, param.getRiskParam(), scene, result);
            return result;
        }
        // 执行风控
        ExecuteRiskResult result = executeRisk(riskId, param, strategies);
        // 保存日志
        recordService.save(riskId, start, param.getRiskParam(), scene, result);
        return result;
    }

    private ExecuteRiskResult executeRisk(long riskId, ExecuteRiskParam param, List<Strategy> strategies) {
        try {
            JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(param.getRiskParam()));
            JSONArray jsonArray = jsonObject.getJSONArray(IRiskConstants.JSON_ARRAY_COLUMN);

            List<Object> riskParams = Lists.newArrayList();
            // 判断是否是批量
            if (jsonObject.size() > 1 || Objects.isNull(jsonArray)) {
                riskParams.add(jsonObject);
            } else {
                for (Object object : jsonArray) {
                    if (object instanceof JSONArray) {
                        riskParams.addAll((JSONArray) object);
                    } else {
                        riskParams.add(object);
                    }
                }
            }
            // 批量执行策略规则，单个的也转成批量的处理
            HashMap<Integer, ExecuteRiskResult> resultMap = Maps.newHashMap();
            for (Object riskParam : riskParams) {
                ExecuteRiskResult result = executeRisk(riskId, riskParam, strategies);
                if (Objects.nonNull(result) && !resultMap.containsKey(result.getResultType())) {
                    resultMap.put(result.getResultType(), result);
                }
            }
            ResultTypeEnum[] enums = ResultTypeEnum.values();
            // 批量处理，有一个拒绝返回拒绝，没有拒绝的有审核返回审核，都通过才算通过
            for (int i = enums.length - 1; i > 0; i--) {
                if (resultMap.containsKey(enums[i].getId())) {
                    return resultMap.get(enums[i].getId());
                }
            }
            return getNotHitResult(riskId, "未命中任何策略，" + param.getSceneCode());
        } catch (Exception e) {
            log.error("executeRisk error {} {}", riskId, JsonUtils.toJson(param), e);
            return getNotHitResult(riskId, e.getMessage());
        }
    }

    private ExecuteRiskResult executeRisk(long riskId, Object riskParam, List<Strategy> strategies) {
        ExecuteRiskResult result = null;
        try {
            // 循环策略执行规则
            for (Strategy strategy : strategies) {
                String scripts = strategyService.getScripts(strategy);
                Set<Integer> featureIds = strategyService.getFeatureIds(strategy);
                Map<String, Object> featureValues = getFeatureValueMap(riskId, riskParam, featureIds);
                log.info("executeRisk {} {} {} {}", riskId, strategy.getId(), scripts, JsonUtils.toJson(featureValues));
                Integer decisionId = runScripts(riskId, scripts, featureValues);
                log.info("executeRisk {} {} {}", riskId, strategy.getId(), decisionId);

                boolean execute = false;
                if (Objects.nonNull(decisionId) && Objects.isNull(result) && StrategyStatusEnum.isOnline(strategy.getStatus())) {
                    execute = true;
                    result = getExecuteRiskResult(riskId, riskParam, decisionId);
                }
                strategyRecordService.save(riskId, strategy, scripts, featureValues, decisionId, execute, result);
            }
        } catch (Exception e) {
            log.error("executeRisk {} {}", riskId, JsonUtils.toJson(riskParam), e);
        }
        return result;
    }

    private ExecuteRiskResult getExecuteRiskResult(long riskId, Object riskParam, Integer decisionId) {
        ExecuteRiskResult result = new ExecuteRiskResult();
        result.setRiskId(riskId);
        Decision decision = decisionService.get(decisionId);
        if (Objects.nonNull(decision)) {
            result.setResultType(decision.getResultType());
            result.setResultMsg(getVariablesMsg(riskId, riskParam, decision.getResultMsg()));
            result.setTemplateId(decision.getTemplateId());
            result.setNoticeMsg(getVariablesMsg(riskId, riskParam, decision.getNoticeMsg()));
            if (!CollectionUtils.isEmpty(decision.getNoticeUsers())) {
                result.setNoticeUsers(getFeatureUsers(riskId, riskParam, decision.getNoticeUsers()));
            }
        }
        return result;
    }

    private Integer runScripts(long riskId, String scripts, Map<String, Object> featureValues) {
        try {
            log.info("runScripts {} {} {}", riskId, scripts, JsonUtils.toJson(featureValues));
            return (Integer) GroovyScriptUtil.invokeMethod(scripts, IRiskConstants.SCRIPT_METHOD, new Object[]{featureValues});
        } catch (Exception e) {
            log.error("runScripts error {} {} {}", riskId, scripts, featureValues, e);
        }
        return null;
    }

    private String getVariablesMsg(long riskId, Object riskParam, String msg) {
        Set<Integer> featureIds = TextUtil.getVariables(msg);
        log.info("getVariablesMsg {} {} {}", riskId, msg, featureIds);
        if (CollectionUtils.isEmpty(featureIds)) {
            return msg;
        }
        List<FeatureValue> featureValues = featureService.listFeatureValues(riskId, riskParam, featureIds);
        try {
            Map<String, String> featureValueMap = Maps.newHashMap();
            for (FeatureValue featureValue : featureValues) {
                Object value = Objects.isNull(featureValue.getValue()) ? StringUtils.EMPTY : featureValue.getValue();
                featureValueMap.put(featureValue.getId().toString(), value.toString());
            }
            String variablesMsg = new StringSubstitutor(featureValueMap).replace(msg);
            log.info("getVariablesMsg {} {}", riskId, variablesMsg);
            return variablesMsg;
        } catch (Exception e) {
            log.error("getVariablesMsg error {} {} {}", riskId, msg, JsonUtils.toJson(featureValues), e);
        }
        return msg;
    }

    private List<String> getFeatureUsers(long riskId, Object riskParam, Set<Integer> featureIds) {
        List<String> users = Lists.newArrayList();
        List<FeatureValue> featureValues = featureService.listFeatureValues(riskId, riskParam, featureIds);
        for (FeatureValue featureValue : featureValues) {
            if (Objects.nonNull(featureValue.getValue())) {
                users.add(featureValue.getValue().toString());
            }
        }
        return users;
    }

    private Map<String, Object> getFeatureValueMap(long riskId, Object riskParam, Set<Integer> featureIds) {
        Map<String, Object> featureValueMap = Maps.newHashMap();
        List<FeatureValue> featureValues = featureService.listFeatureValues(riskId, riskParam, featureIds);
        for (FeatureValue featureValue : featureValues) {
            featureValueMap.put(IRiskConstants.COLUMN_PREFIX + featureValue.getId(), featureValue.getValue());
        }
        return featureValueMap;
    }

    private ExecuteRiskResult getNotHitResult(long riskId, String resultMsg) {
        ExecuteRiskResult result = new ExecuteRiskResult();
        result.setRiskId(riskId);
        result.setResultType(ExecuteRiskResult.NOT_HIT);
        result.setResultMsg(resultMsg);
        return result;
    }
}
